﻿using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Mvc;
using LogikBug.Collections;
using LogikBug.ModularMvc.Attributes;
using LogikBug.ModularMvc.Events;
using LogikBug.ModularMvc.Factories;
using LogikBug.ModularMvc.Locations;
using LogikBug.ModularMvc.Navigation;
using LogikBug.ModularMvc.Registration;

namespace LogikBug.ModularMvc
{
	public class ModuleProvider : DisposableObject, IModuleProvider
	{
		#region Fields

		private static IModuleProvider _currentProvider;
		private static readonly IModuleProvider DefaultProvider = new ModuleProvider();

		private IMenuFactory _menuFactory;

		#endregion

		#region Constructors

		protected ModuleProvider()
		{
			Modules = new Hashtable<object, IModule>();
		}

		#endregion

		#region Events

		public event RegistrationCompleted ModuleRegistrationCompleted;

		#endregion

		#region Properties

		public static IModuleProvider Current
		{
			get
			{
				return GetCurrentProvider();
			}
		}

		public IModule this[object key]
		{
			get
			{
				if (Modules.Has(key))
				{
					return Modules[key];
				}

				return null;
			}
		}

		public IMenuFactory MenuFactory
		{
			get
			{
				return _menuFactory ?? (_menuFactory = MenuFactoryProvider.DefaultMenuFactory);
			}
			set
			{
				_menuFactory = value;
			}
		}

		public bool IsInitialized { get; protected set; }

		public IRegistrationContext RegistrationContext { get; protected set; }

		protected IHashtable<object, IModule> Modules { get; private set; }

		#endregion

		#region Methods

		#region Static Methods

		internal static void SetCurrentProviderInternal(IModuleProvider provider)
		{
			if (provider != null)
			{
				_currentProvider = provider;
			}
		}

		public static IModuleProvider GetCurrentProvider()
		{
			return _currentProvider ?? DefaultProvider;
		}

		public static void SetCurrentProvider<T>(T moduleProvider) where T : IModuleProvider
		{
			SetCurrentProviderInternal(moduleProvider);
		}

		#endregion

		#region IModuleProvider Methods

		public bool HasModule(object key)
		{
			return Modules.Has(key);
		}

		public void SetEnabled(object modelKey, bool enabled)
		{
			if (Modules.Has(modelKey))
			{
				this[modelKey].IsEnabled = enabled;
			}
		}

		public virtual void Initialize(ILocation location, IRegistrationContext context)
		{
			Initialize(new[] { location }, RegistrationContext = context.ThrowIfNull());
		}

		public virtual void Initialize(IEnumerable<ILocation> locations, IRegistrationContext context)
		{
			RegistrationContext = context.ThrowIfNull();
			GetModules(locations, context);
			IsInitialized = true;
		}

		public virtual MvcHtmlString GenerateMenu(ViewContext viewContext)
		{
			return GenerateMenu(viewContext, MenuType.Main);
		}

		public virtual MvcHtmlString GenerateMenu(ViewContext viewContext, MenuType menuType)
		{
			return MenuFactory.Generate(viewContext, menuType);
		}

		protected virtual void GetModules(ILocation location, IRegistrationContext context)
		{
			context.ThrowIfNull();
			location.ThrowIfNull();

			var modules = new List<IModule>();

			foreach (var item in location)
			{
				var assembly = Assembly.LoadFile(item);

				if (assembly == null) { continue; }

				var registerModuleAttributes = assembly.GetAttributes<RegisterModuleAttribute>();
				var currentModules = registerModuleAttributes.GetMvcModules();

				if (!currentModules.Any(a => a != null))
				{
					currentModules = assembly.GetMvcModules();
				}

				if (currentModules.Count(m => m != null) < 1) { continue; }

				modules.AddRange(currentModules.Where(m => m != null));
			}

			RegisterModules(modules, context);
		}

		protected virtual void GetModules(IEnumerable<ILocation> locations, IRegistrationContext context)
		{
			context.ThrowIfNull();
			locations.ThrowIfNull();

			foreach (var location in locations)
			{
				GetModules(location, context);
			}

			InvokeModuleRegistrationCompleted(new RegistrationCompletedEventArgs(this));
		}

		protected virtual void RegisterModules(IEnumerable<IModule> modules, IRegistrationContext context)
		{
			var orderedModules = modules.OrderBy(m => m.OrderPrecedence);

			foreach (var module in orderedModules)
			{
				RegisterModule(module, context);
			}
		}

		protected virtual void RegisterModule(IModule module, IRegistrationContext context)
		{
			module.Initialize(this, context);

			if (!Modules.Has(module.Key))
			{
				Modules.Add(module.Key, module);
			}
		}

		protected virtual void InvokeModuleRegistrationCompleted(RegistrationCompletedEventArgs e)
		{
			var handler = ModuleRegistrationCompleted;

			if (handler != null)
			{
				handler(this, e);
			}
		}

		protected internal override void DisposeResources()
		{
			foreach (var item in this)
			{
				item.Dispose();
			}

			Modules.Clear();
		}

		#endregion

		#region IEmumerable Overrides

		public virtual IEnumerator<IModule> GetEnumerator()
		{
			return Modules.Values.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion

		#endregion
	}
}
